'''
Created on Mar 4, 2014

@author: Benjamin Brewer
@license: GNU GPL v3
'''

import unittest
from me.benbrewer.exercise.vending.vending_machine import VendingMachine
from me.benbrewer.exercise.vending.coin import Nickel, Dime, Quarter, Penny
from me.benbrewer.exercise.vending.product import Cola, Chips, Candy

class Functional(unittest.TestCase):
    def setUp(self):
        self.vending = VendingMachine()

    def tearDown(self):
        pass
    
    def getValidCoins(self):
        return [Nickel(), Dime(), Quarter()]
    
    def getInvalidCoins(self):
        return [Penny()]
    
    def getCoinsForCola(self):
        return [Quarter() for dummy in range(4)]
    
    def getCoinsForChips(self):
        return [Quarter() for dummy in range(2)]
    
    def getCoinsForCandy(self):
        return [Quarter() for dummy in range(2)] + [Dime(), Nickel()]
    
    def getCoinsForChange(self):
        return [Quarter() for dummy in range(5)]
    
    def testInsertCoins(self):
        # explicit setUp call to make the context clear
        self.setUp()
        
        # put some coins in our pocket
        validCoins = self.getValidCoins()
        invalidCoins = self.getInvalidCoins()
        
        # walk up to machine, assuming that we've not inserted coins
        self.assertEquals("INSERT COIN", self.vending.display, "Machine with no coins reports: " + self.vending.display)
        
        # insert each type of valid coin and verify that they are not rejected
        self.assertListEqual([True for dummy in self.getValidCoins()], [self.vending.insertCoin(c) for c in validCoins], "Machine rejects valid coins")
        
        # insert each type of invalid coin and verify that it is rejected
        self.assertListEqual([False for dummy in self.getInvalidCoins()], [self.vending.insertCoin(c) for c in invalidCoins], "Machine accepts invalid coins")
        
        # actually see if valid coins update the display - since we're doing this inside of a context, we assume that coins have been added
        # @todo: this should really be broken out into separate unit tests to verify that each coin returns the correct result for display
        self.assertEquals("$0.40", self.vending.display, "Machine with that should have $0.40 inserted says we have: " + self.vending.display)
        
        # actually check the coin return to see if rejected coins show up there - this makes up for our possibly wrong assumption that insertCoin will give some sort of rejection notification
        self.assertListEqual(invalidCoins, self.vending.coinReturn)
        
        # further tests would expand the definition of invalid coin to include more rigorous examples (e.g. masses, diameters, etc. within a reasonable number of significant digits) 
        # however, in the interest of time, moving forward to product select tests
        
    def testSelectProducts(self):
        # get our coins ready
        colaCoins = self.getCoinsForCola()
        chipsCoins = self.getCoinsForChips()
        candyCoins = self.getCoinsForCandy()
        
        # trying to make the sequence clear on the following, so no comprehensions or other shortcuts
        # cola button press
        self.setUp()
        [self.vending.insertCoin(c) for c in colaCoins]
        self.assertEqual(True, self.vending.pressButton(Cola()), "Machine reports that we didn't have enough money for Cola")
        self.assertEqual("THANK YOU", self.vending.display, "Machine is being rude, it didn't thank us")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # want to test that price has been wiped and that display is correct
        self.assertEqual(False, self.vending.pressButton(Cola()), "Machine reports that we somehow had enough  money for Cola")
        self.assertEqual("PRICE $1.00", self.vending.display, "Machine doesn't give us an indication of how much we owe")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # another test sequence with actual value
        self.vending.insertCoin(Quarter())
        self.assertEqual(False, self.vending.pressButton(Cola()), "Machine reports that we somehow had enough  money for Cola")
        self.assertEqual("PRICE $1.00", self.vending.display, "Machine doesn't give us an indication of how much we owe")
        self.assertEqual("$0.25", self.vending.display, "Machine is not being helpful, it is not telling me how much I have put in.")
        
        # test chip button press
        self.setUp()
        [self.vending.insertCoin(c) for c in chipsCoins]
        self.assertEqual(True, self.vending.pressButton(Chips()), "Machine reports that we didn't have enough money for Chips")
        self.assertEqual("THANK YOU", self.vending.display, "Machine is being rude, it didn't thank us")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # want to test that price has been wiped and that display is correct
        self.assertEqual(False, self.vending.pressButton(Chips()), "Machine reports that we somehow had enough  money for Chips")
        self.assertEqual("PRICE $0.50", self.vending.display, "Machine doesn't give us an indication of how much we owe")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # another test sequence with actual value
        self.vending.insertCoin(Quarter())
        self.assertEqual(False, self.vending.pressButton(Chips()), "Machine reports that we somehow had enough  money for Chips")
        self.assertEqual("PRICE $0.50", self.vending.display, "Machine doesn't give us an indication of how much we owe")
        self.assertEqual("$0.25", self.vending.display, "Machine is not being helpful, it is not telling me how much I have put in.")
        
        # test chip button press
        self.setUp()
        [self.vending.insertCoin(c) for c in candyCoins]
        self.assertEqual(True, self.vending.pressButton(Candy()), "Machine reports that we didn't have enough money for Candy")
        self.assertEqual("THANK YOU", self.vending.display, "Machine is being rude, it didn't thank us")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # want to test that price has been wiped and that display is correct
        self.assertEqual(False, self.vending.pressButton(Candy()), "Machine reports that we somehow had enough  money for Candy")
        self.assertEqual("PRICE $0.65", self.vending.display, "Machine doesn't give us an indication of how much we owe")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # another test sequence with actual value
        self.vending.insertCoin(Quarter())
        self.assertEqual(False, self.vending.pressButton(Candy()), "Machine reports that we somehow had enough  money for Candy")
        self.assertEqual("PRICE $0.65", self.vending.display, "Machine doesn't give us an indication of how much we owe")
        self.assertEqual("$0.25", self.vending.display, "Machine is not being helpful, it is not telling me how much I have put in.")
        
        # further tests would try these as individual units and also attempt to try most permutations or some sort of proof through induction
        # also, it's quite apparent that the specific sequencing of these tests doesn't uncover an obvious flaw -- that the total will not get updated in the vending machine if the display is not checked
        # however, in the interest of time, moving forward to change making tests
        
    def testMakeChange(self):
        # get the change coins - $1.25
        changeCoins = self.getCoinsForChange()
        
        # once again, trying to make the sequence clear by not taking Pythonic shortcuts
        # test getting change back for a cola
        self.setUp()
        [self.vending.insertCoin(c) for c in changeCoins]
        self.vending.pressButton(Cola())
        self.assertEqual(1, len(self.vending.coinReturn))
        self.assertListEqual([Quarter], [self.vending.coinReturn[0].__class__], "Machine didn't return the right kind of change")
        self.assertEqual("THANK YOU", self.vending.display, "Machine is being rude, it didn't thank us")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # test getting change back for a chips
        self.setUp()
        [self.vending.insertCoin(c) for c in changeCoins]
        self.vending.pressButton(Chips())
        self.assertEqual(3, len(self.vending.coinReturn))
        self.assertListEqual([Quarter for dummy in range(3)], [c.__class__ for c in self.vending.coinReturn], "Machine didn't return the right kind of change")
        self.assertEqual("THANK YOU", self.vending.display, "Machine is being rude, it didn't thank us")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # test getting change back for a candy
        self.setUp()
        [self.vending.insertCoin(c) for c in changeCoins]
        self.vending.pressButton(Candy())
        self.assertEqual(3, len(self.vending.coinReturn))
        self.assertListEqual([Quarter for dummy in range(2)] + [Dime], [c.__class__ for c in self.vending.coinReturn], "Machine didn't return the right kind of change")
        self.assertEqual("THANK YOU", self.vending.display, "Machine is being rude, it didn't thank us")
        self.assertEqual("INSERT COIN", self.vending.display, "Machine is not being helpful and didn't tell us what to do next")
        
        # since this one starts to get into some larger algorithms, it is a perfect candidate for being broken down into unit tests, especially for some of questional
        # functionality, such as Decimal and rounding
        
if __name__ == "__main__":
    # import sys;sys.argv = ['', 'Test.testName']
    unittest.main()
